﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Concurrency;
using System.Windows;

namespace ReactiveCode
{
    using Observables;
    using Observables.Expressions;
    using Statements;
    using Util;

    public interface IStateDecorator
    {
        IObservable<T> Apply<T>(IObservable<T> state);
        ISubject<T> Apply<T>(ISubject<T> state);
    }
    public delegate TResult ObservableFactory<TResult>(IStateDecorator stateDecorator);

    interface IStatementParent
    {
        IList<ReactiveStatement> Children { get; }
    }
    public partial class ReactiveStatement : IStateDecorator
    {
        #region Hierarchy
        Variable<ReactiveStatement> _parent = new Variable<ReactiveStatement>();
        internal ReactiveStatement Parent
        {
            get { return _parent.Value; }
            set
            {
                Contract.Requires(!(this is RootStatement));
                Contract.Requires((value == null) != (Parent == null));
                _parent.Value = value;
            }
        }

        internal IEnumerable<ReactiveStatement> Ancestors()
        {
            return MeAndAncestors().Skip(1);
        }
        internal IEnumerable<ReactiveStatement> MeAndAncestors()
        {
            for (var stmt = this; stmt != null; stmt = stmt.Parent)
                yield return stmt;
        }

        Variable<ReactiveMethod> _method = new Variable<ReactiveMethod>();
        internal ReactiveMethod Method
        {
            get { return _method.Value; }
            set
            {
                Contract.Requires(this is ReactiveMethod.BodyStatement);
                _method.Value = value;
            }
        }

        Variable<RootStatement> _root = new Variable<RootStatement>();
        internal RootStatement Root
        {
            get { return _root.Value; }
            set
            {
                Contract.Requires(this is RootStatement);
                _root.Value = value;
            }
        }

        internal ReactiveHost Host
        {
            get { return Root != null ? Root.Host : null; }
        }
        #endregion

        #region Enabled property

        public bool Enabled
        {
            get { return EnabledProperty.Value; }
            internal set
            {
                Contract.Requires(!value || Host != null);
                EnabledProperty.Value = value;
            }
        }
        internal Variable<bool> EnabledProperty { get; private set; }
        internal IDisposable BindEnabled(ReactiveStatement other)
        {
            Contract.Requires(other != null);
            return EnabledProperty.Bind(other.EnabledProperty);
        }

        #endregion

        public string Name { get; set; }

        internal ReactiveStatement()
        {
            EnabledProperty = new Variable<bool>(false);
            _parent = new Variable<ReactiveStatement>();
            _parent.SelectMany(x => x._method).Subscribe(_method);
            _parent.SelectMany(x => x._root).Subscribe(_root);
        }

        public virtual void Dispose()
        {
            Enabled = false;
        }

        #region Current

        [ThreadStatic]
        static Stack<ReactiveStatement> _currentRules;

        internal void PushCurrent()
        {
            if (_currentRules == null)
                _currentRules = new Stack<ReactiveStatement>();
            _currentRules.Push(this);
        }
        internal void PopCurrent()
        {
            if (_currentRules == null)
                throw new InvalidOperationException();

            for (; ; )
            {
                if (_currentRules.Count == 0)
                    throw new InvalidOperationException();
                if (this == _currentRules.Pop())
                    break;
            }
        }

        internal static ReactiveStatement Current
        {
            get
            {
                if (_currentRules == null || !_currentRules.Any())
                    return null;
                else
                    return _currentRules.Peek();
            }
        }

        internal bool IsHigherPriority(ReactiveStatement than)
        {
            return CompareOrder(this, than) > 0;
        }
        int? CompareWithCurrent()
        {
            return CompareOrder(this, Current);
        }

        #endregion

        #region State changes

        internal StateChangeScheduler Scheduler
        {
            get { return Host.Scheduler; }
        }

        internal virtual bool MarkAsCurrent
        {
            get { return true; }
        }

        IObservable<T> IStateDecorator.Apply<T>(IObservable<T> state)
        {
            state = state.SubscribeIf(EnabledProperty);
            return Observable.CreateWithDisposable<T>(o => state.Subscribe(
                x =>
                {
                    int? cmp = CompareWithCurrent();
                    if (cmp != null && cmp <= 0)
                        return;

                    Contract.Assert(Scheduler != null);
                    Scheduler.ScheduleNotification(this, o, () => o.OnNext(x));
                }, o.OnError, o.OnCompleted));
        }
        ISubject<T> IStateDecorator.Apply<T>(ISubject<T> state)
        {
            var observable = (state as IObservable<T>).Decorate(this);
            var observer = Observer.Create<T>(
                onNext: x =>
                {
                    Contract.Assert(Scheduler != null);
                    Scheduler.ScheduleChange(delegate
                    {
                        bool markAsCurrent = MarkAsCurrent;
                        if (markAsCurrent)
                            PushCurrent();
                        try
                        {
                            state.OnNext(x);
                        }
                        finally
                        {
                            if (markAsCurrent)
                                PopCurrent();
                        }
                    });
                }, 
                onError: state.OnError, 
                onCompleted: state.OnCompleted);
            return Subject.Create(observable, observer);
        }
        internal IObservable<T> Extract<T>(ObservableFactory<IObservable<T>> factory)
        {
            return factory(this).SubscribeIf(EnabledProperty);
        }
        internal ISubject<T> Extract<T>(ObservableFactory<ISubject<T>> factory)
        {
            var subject = factory(this);
            return Subject.Create(
                subject.SubscribeIf(EnabledProperty),
                subject
            );
        }
        internal IObservableEnumerable<T> Extract<T>(ObservableFactory<IObservableEnumerable<T>> factory)
        {
            return factory(this); // TODO: apply decorator on IObservableEnumerable<T>
        }

        #endregion

        #region Creation

        static ObservableFactory<IObservable<T>> ToObservable<T>(Expression<Func<T>> expression)
        {
            return ExpressionConvert.ToObservable<T>(expression.Body);
        }

        public static ReactiveStatement If(ObservableFactory<IObservable<bool>> test, ReactiveStatement trueStatement, ReactiveStatement falseStatement = null)
        {
            return new IfStatement(test, trueStatement, falseStatement);
        }
        public static ReactiveStatement If(Expression<Func<bool>> test, ReactiveStatement trueStatement, ReactiveStatement falseStatement = null)
        {
            Contract.Requires(test != null);
            var result = If(ToObservable(test), trueStatement, falseStatement);
            result.Name = "if " + test.Body;
            return result;
        }

        public static ReactiveStatement Assignment<T>(ObservableFactory<ISubject<T>> target, ObservableFactory<IObservable<T>> value)
        {
            return new Assignment<T>(target, value);
        }
        public static ReactiveStatement Assignment<T>(Expression<Func<T>> target, Expression<Func<T>> value)
        {
            Contract.Requires(target != null && value != null);
            var result = Assignment(ExpressionConvert.ToSubject<T>(target.Body), ToObservable(value));
            result.Name = string.Format("{0} = {1}", target.Body, value.Body);
            return result;
        }
        public static ReactiveStatement Assignment(Expression<Func<DependencyObject>> dependencyObject, DependencyProperty property, Expression<Func<object>> value)
        {
            Contract.Requires(dependencyObject != null && property != null && value != null);
            var observableObject = ToObservable(dependencyObject);
            var instanceFunc = dependencyObject.Compile();
            ObservableFactory<ISubject<object>> subject = selector =>
            {
                var instance = observableObject(selector);
                var observable = Observable.CreateWithDisposable<object>(o => instance.Subscribe(x => new ChangeListening.DependencyPropertyListenerMediator(x, property, o)));
                return Subject.Create<object>(observable, x => instanceFunc().SetValue(property, x));
            };
            return Assignment(subject, ToObservable(value));
        }
        public static ReactiveStatement Assignment<T>(Expression<Func<T>> target, T constantValue)
        {
            Contract.Requires(target != null);
            var result = Assignment(ExpressionConvert.ToSubject<T>(target.Body), _ => Observable.Return(constantValue));
            result.Name = string.Format("{0} = {1}", target.Body, constantValue);
            return result;
        }

        public static ReactiveStatement ForEach<T>(ObservableFactory<IObservableEnumerable<T>> source, Func<IObservable<T>, ReactiveStatement> body)
        {
            Contract.Requires(source != null && body != null);
            return new ForEach<T>(source, body);
        }

        public static ReactiveStatement Block(params ReactiveStatement[] body)
        {
            return Block(body.AsEnumerable());
        }
        public static ReactiveStatement Block(IEnumerable<ReactiveStatement> body)
        {
            var block = new Block();
            foreach (var stmt in body)
                block.Body.Add(stmt);
            return block;
        }

        public static ReactiveStatement Call(ReactiveMethod method)
        {
            return new MethodCall(method);
        }

        public static ReactiveStatement Return<T>(ObservableFactory<IObservable<T>> value)
        {
            return new ReturnStatement<T>(value);
        }
        public static ReactiveStatement Return<T>(Expression<Func<T>> value)
        {
            return Return(ToObservable(value));
        }
        public static ReactiveStatement Return()
        {
            return Return<Unit>(_ => Observable.Return(new Unit()));
        }

        public static ReactiveStatement Imperative(ObservableFactory<IObservable<Unit>> trigger, Action body)
        {
            Contract.Requires(trigger != null && body != null);
            return new ImperativeStatement(trigger, body);
        }

        public static ReactiveStatement Nop()
        {
            return new ReactiveStatement();
        }

        #endregion

        public override string ToString()
        {
            return Name ?? base.ToString();
        }
    }
}